/** @file    SecurityManager.cpp
 *  @author  Alessandro Polo
 *  @version $Id: SecurityManager.cpp 2832 2010-08-02 22:53:46Z alex $
 *  @brief
 * File containing methods for the wosh::SecurityManager class.
 * The header for this class can be found in SecurityManager.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <core/SecurityManager.h>

 #include <core/ObjectAllocator.h>
 #include <core/BusRing.h>
 #include <core/UserManager.h>
 #include <core/BusManager.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/List.h>
 #include <core/Table.h>
 #include <core/Fact.h>
 #include <core/ObjectFactory.h>
 #include <stdio.h> // TEMP, TO BE REMOVED


using namespace std;
namespace wosh {

// Gamma singleton! static reference is allocated and maintained by WoshKernel
extern "C" {
	SecurityManager* SecurityManager::getInstance() { 
		if ( WoshKernel::getInstance() == NULL ) return NULL;
		return &WoshKernel::getInstance()->security();
	}
}
 // register as abstract because it is a singleton:
 WOSH_REGISTER_INTERFACE(wosh::SecurityManager, "wosh::WoshModule", 0.01, _static_SecurityManager )

 WOSH_REGISTER_INTERFACE(wosh::SecurityAuthentication, "", 0.01, _static_SecurityAuthentication )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

SecurityManager::SecurityManager( const WoshKernel* parent )
	: WoshModule(parent, _SecurityManager_NAME, _SecurityManager_URI) {

//Log.setLevel( VERBOSE );

	Log(LOG_DEBUG, " Initializing [LogMaxSize=%ld; LogLocally=true]..", _SecurityManager_DEFAULT_EventLogMaxSize );
	this->eventLogMaxSize = _SecurityManager_DEFAULT_EventLogMaxSize;
	this->eventLogLocallyOnly = true;

	Log(LOG_DEBUG, " Initializing Properties.." );
	Properties.set( _SecurityManager_KEY_EventLogMaxSize,	this->eventLogMaxSize, Permission::RW_RW_R__() );
	Properties.monitor(_SecurityManager_KEY_EventLogMaxSize);

	Log(LOG_DEBUG, " Registering Methods.." );
	MethodRequest* mmEventsLog = Methods.create<MethodRequest>( _SecurityManager_METHOD_eventslog, "list Security' events", Permission::R_XR__R__() );
	mmEventsLog->setMethod( this, (MethodRequestPtr)&SecurityManager::mmDoEventsLog );
	MethodWRESULT* mmClearLog = Methods.create<MethodWRESULT>( _SecurityManager_METHOD_clear_log, "clear events log", Permission::R_XR__R__() );
	mmClearLog->setMethod( this, (MethodWRESULTPtr)&SecurityManager::clearEventsLog );

	MethodRequest* mmFakeSecurityEvent = Methods.create<MethodRequest>( _SecurityManager_METHOD_fake_event, "fake Security event", Permission::R_XR__R__() );
	mmFakeSecurityEvent->setMethod( this, (MethodRequestPtr)&SecurityManager::mmDoFakeSecurityEvent );

 }

SecurityManager::~SecurityManager() {
	Log(LOG_DEBUG, ":~SecurityManager() Destroying.." );

	Log(LOG_VERBOSE, ":~SecurityManager() Freeing EventsLog [%d]..", this->eventsLog.count() );
	this->eventsLog.clear();

	Log(LOG_VERBOSE, " Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////// KERNEL EVENTS

WRESULT SecurityManager::onKernelInitializing() {
	WRESULT ret = WoshModule::onKernelInitializing();

	Log(LOG_VERBOSE, " Initializing BusRing ("_Bus_Security").." );
	Bus* securityBus = new BusRing(_Bus_Security);
	securityBus->setParentObject(this);
	securityBus->setSharedLocal(false);
	securityBus->setSharedNetwork(true);
	WRESULT created_bus = WoshKernel::getInstance()->busses().registerBus(securityBus);
	if ( WFAILED(created_bus) )
		Log(LOG_CRITICAL, " FAILED#%d Creating BusRing ("_Bus_Security")", created_bus );
	else
		Log(LOG_INFO, " Created BusRing ("_Bus_Security")." );

	BusSecurity.setMessageHandler(this);
	WRESULT bus_d_connected = BusSecurity.connect( _Bus_Security );
	if ( WFAILED(bus_d_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "init", "FAILED#%d Connecting Bus ("_Bus_Security")..", bus_d_connected );

	return ret + bus_d_connected;
 }

/////////////////////////////////////////////////////////////////////////////////////////////// KERNEL EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTs

bool SecurityManager::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source )
 {
	if ( property_current.getKey() == _SecurityManager_KEY_EventLogMaxSize ) {
		this->eventsLog.transactionBeginWrite();
		this->eventLogMaxSize = value_proposed.toLong(this->eventLogMaxSize);
		this->eventsLog.transactionEnd();
		return true;
	 }
	else if ( property_current.getKey() == _SecurityManager_KEY_EventLogLocallyOnly ) {
		this->eventsLog.transactionBeginWrite();
		this->eventLogLocallyOnly = value_proposed.toBoolean(this->eventLogLocallyOnly);
		this->eventsLog.transactionEnd();
		return true;
	 }
	// forward processing to base class
	return WoshModule::updatingProperty(do_update, value_proposed, property_current, source);
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTs
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// BUS EVENTS

void SecurityManager::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	if ( message.getContent()->isFact() && message.getSourceBus() == _Bus_Security ) {
		logEvent(message);
		return;
	 }
	if ( !message.getContent()->isRequest() ) return;
	// avoid message replica
	if ( !MessageFilter::filterReplica( message, &BusCore ) )
		return;
	WoshModule::busMessage( message, source );
 }

////////////////////////////////////////////////////////////////////////////////////////////////// BUS EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void SecurityManager::logEvent( const Message& message ) {
	if ( this->eventLogMaxSize == 0 ) return;
	if ( this->eventLogLocallyOnly  && !message.getSource().isKernelEmptyOrLocal() ) return;
	this->eventsLog.transactionBeginWrite();
	if ( this->eventsLog.size() >= this->eventLogMaxSize ) {
		this->eventsLog.erase( this->eventsLog.begin(), true );
	 }
	this->eventsLog.push_back( message.clone() );
	this->eventsLog.transactionEnd();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// STATIC METHODS

bool SecurityManager::compareMask( short mask_pattern, short operation )
 {
	///@todo using bitset
	if ( mask_pattern == 7 ) return true;
	if ( mask_pattern == operation ) return true;
	if ( operation == 1 ) {
		if ( mask_pattern == 3 || mask_pattern == 5 )
			return true;
		return false;
	 }
	if ( operation == 2 ) {
		if ( mask_pattern == 3 || mask_pattern == 6 )
			return true;
		return false;
	 }
	if ( operation == 4 ) {
		if ( mask_pattern == 5 || mask_pattern == 6 )
			return true;
		return false;
	 }
	return false;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool SecurityManager::evalAuthorization( const SecurityToken* security, const Permission* permissions, Permission::PERMISSION_TYPE operation )
 {
	if ( permissions == NULL ) {
		printf("SecurityManager::evalAuthorization() BUG? permissions == NULL" );
		return false;
	 }
	if ( security == NULL ) {
		printf("SecurityManager::evalAuthorization() BUG? security == NULL" );
		return false;
	 }

	// is root accessing?
	if ( security->getUsername() == User::root ) {// || UserManager::isGroupOfUser( security->getUser(), "root" ) ) {
		// any of the mask validates
//		if ( compareMask( permissions->getMask(0), operation ) || compareMask( permissions->getMask(1), operation ) || compareMask( permissions->getMask(2), operation ) )
//			return true;
//		return false;
		if ( permissions->getMask(0) == Permission::None && permissions->getMask(1) == Permission::None && permissions->getMask(2) == Permission::None )
			return false;
		if ( operation != Permission::Read && permissions->getMask(0) == Permission::Read && permissions->getMask(1) == Permission::Read && permissions->getMask(2) == Permission::Read )
			return false;

		if ( compareMask( permissions->getMask(0), operation ) || compareMask( permissions->getMask(1), operation ) || compareMask( permissions->getMask(2), operation ) )
			return true;
		
		printf("SecurityManager::evalAuthorization() WARNING, root allowed BUT probably a permission [%s] BUG!\n", permissions->toString().c_str());
		return true;
	 }

	// is the owner?
	if ( security->getUsername() == permissions->getOwner() ) {
		// check owner rights
		if ( compareMask( permissions->getMask(0), operation ) )
			return true;
	 }

	// check group rights
	if ( UserManager::isGroupOfUser( security->getUsername(), permissions->getGroup() ) ) {
		if ( compareMask( permissions->getMask(1), (short)operation ) )
			return true;
	 }

	// check others rights
	if ( compareMask( permissions->getMask(2), (short)operation ) )
		return true;

	return false;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT SecurityManager::validateToken( const SecurityToken* token ) {
	if ( token == NULL ) return WRET_ERR_PARAM;

	SecurityTokenSignature* signature = getTokenSignatures().find(token->getID());
	if ( signature == NULL ) {

	 }

	SecurityTokenAuthenticator* sa = getAuthenticators().find(token->getClassName());
	if ( sa == NULL ) return WRET_ERR_PARAM;
	return sa->validateToken(token);
 }

// TEMP, TO BE REMOVED
SecurityToken* SecurityManager::generateToken( const Object* object ) {
	if ( object == NULL ) return NULL;
	return generateToken(object->getPermission().getOwner(), object);
 }

// TEMP, TO BE REMOVED
SecurityToken* SecurityManager::generateToken( const std::string& wosh_user, const Object* object ) {
	if ( !UserManager::isUser(wosh_user) ) return NULL;
	SecurityTokenImplicit* st = new SecurityTokenImplicit();
	st->user_name = wosh_user;
	if ( object != NULL )
		st->creator_uuid = object->getEntityID();
	return st;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT SecurityManager::validate( const WoshHost* source, const WoshHost* target, const NetworkChannel* channel ) {
	(void)source; (void)target; (void)channel; 
	return WRET_OK;
 }

WRESULT SecurityManager::validate( const Message* message, const NetworkChannel* channel ) {
	(void)message; (void)channel; 
	return WRET_OK;
 }
WRESULT SecurityManager::validate( const Message* message, const Bus* source, const Bus* target ) {
	(void)message; (void)source; (void)target; 
	return WRET_OK;
 }

////////////////////////////////////////////////////////////////////////////////////////////// STATIC METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT SecurityManager::clearEventsLog() {
	Log(LOG_VERBOSE, "clearEventsLog()" );
	this->eventsLog.clear();
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* SecurityManager::mmDoFakeSecurityEvent( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant dSourceName; Variant dEventName; Variant dEventData;
	request->extractArguments(dSourceName, dEventName, dEventData);
	if ( !dSourceName.isStringNotEmpty() || !dEventName.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [source-uri, event-name]");
	Log(LOG_VERBOSE, "mmDoFakeSecurityEvent(%s) Fact: %s [%s]", dSourceName.asString().c_str(), dEventName.asString().c_str(), dEventData.toString().c_str() );
	Message* msg_event = new Message();
	msg_event->setSource( this );
	msg_event->getSource().setParent(this->getURI());
	msg_event->getSource().setName( dSourceName.asString() );
	msg_event->setContent( new Fact(dEventName.asString(), new Variant(dEventData)) );
	msg_event->setDestinationBroadcast();
	BusSecurity.postMessage(msg_event);
	return request->replyResponse(WRET_OK, "Security-Event (message) sent!" );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* SecurityManager::mmDoEventsLog( const Request* request ) {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoEventsLog(%s)", option.c_str() );
	if ( option == "s" || option == "short" ) {
		this->eventsLog.transactionBeginRead();
		List* objs = new List(this->eventsLog.size());
		tMessageList::ConstIterator it;
		const tMessageList::ConstIterator it_end = this->eventsLog.end();
		for ( it=this->eventsLog.begin(); it!=it_end; ++it ) {
			if ( *it == NULL ) continue;
			if ( (*it)->isEmpty() ) continue;
			if ( !(*it)->getContent()->isFact() ) continue;
			objs->push_back<Variant>( (*it)->getContent()->asFact()->getEvent() );
		 }
		this->eventsLog.transactionEnd();
		return request->replyResponse(WRET_OK, objs);
	 }
	else {
		this->eventsLog.transactionBeginRead();
		Table* tbl = new Table( this->eventsLog.size(), 4, true );
		tbl->setTableName("EventLog");
		tbl->setHeaderHorizontal("TimeStamp", 0);
		tbl->setHeaderHorizontal("URI", 1);
		tbl->setHeaderHorizontal("Event", 2);
		tbl->setHeaderHorizontal("Data", 3);
		unsigned int iRow = 0;
		tMessageList::ConstIterator it;
		const tMessageList::ConstIterator it_end = this->eventsLog.end();
		for ( it=this->eventsLog.begin(); it!=it_end; ++it, ++iRow ) {
			if ( *it == NULL ) continue;
			if ( (*it)->isEmpty() ) continue;
			const Fact* fact = (*it)->getContent()->asFact();
			tbl->set( new Variant((*it)->getTimestamp(), Variant::DATETIME), iRow, 0);
			tbl->set( (*it)->getSource().clone(), iRow, 1);
			tbl->set<Variant>( fact->getEvent(), iRow, 2);
			if ( fact->hasData() )
				tbl->set( fact->getData()->clone(), iRow, 3);
			else
				tbl->set<Variant>( "NULL", iRow, 3);
		 }
		this->eventsLog.transactionEnd();
		return request->replyResponse(WRET_OK, tbl);
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
